﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using GFE.SharePoint.Interfaces;
using GFE.SharePoint.Lazy;
using GFE.SharePoint.CSharpExtensions;
using System.Reflection;

namespace GFE.SharePoint.JSON
{
    public class GFEJSONHandler : IHttpHandler
    {
        public const string DATE_TIME_FORMAT = "ddMMyyyy HH:mm:ss";
        /// <summary>
        /// Show the result in JSON 
        /// </summary>
        public const string SHOW_JSON_RESULT = "showjsonresult";

        public bool IsReusable
        {
            get { return false; }
        }

        public Dictionary<string, MethodParameter> Parameters { get; set; }

        public object[] Arguments
        {
            get
            {
                return GetMethodArguments();
            }
        }
        public void ProcessRequest(HttpContext context)
        {
            Parameters = new Dictionary<string, MethodParameter>();

            foreach (var parameter in from formKey in context.Request.Params.Cast<string>()
                                      from formValue in context.Request.Params.GetValues(formKey)
                                      where !IsSystemOption(formKey, formValue)
                                      select new MethodParameter
                                      {
                                          Name = formKey,
                                          Value = formValue == "null" ? null : formValue
                                      })
            {
                Parameters.Add(parameter.Name, parameter);
            }

            var JSONClassName = context.Request.Params["JSONClass"];
            var JSONMethodName = context.Request.Params["JSONMethod"];
            var JSONCatalog = GFECompositionContainer.GetExports<IGFEJSONClassExport>();
            var JSONObject = JSONCatalog.SingleOrDefault(x => x.Value != null && x.Value.GetType().ToString() == JSONClassName);
            var result = default(object);
            if (JSONObject != null) {
                var methodInfo = JSONObject.AsGFEJSONLazy<IGFEJSONClassExport>().GetMethodInfo(JSONMethodName);
                Parameters.Values
                    .ForEach<MethodParameter>
                    (a =>
                    {
                        if ( methodInfo.GetParameters().Any(b => b.Name == a.Name))
                            a.ParameterDetails = methodInfo.GetParameters().Where(b => b.Name == a.Name).First();   
                    });
                result = JSONObject.AsGFEJSONLazy<IGFEJSONClassExport>().InvokeByName(JSONMethodName, Arguments);
            }

            context.Response.ContentType = "application/json";

            var json = string.Empty;
            try
            {
                json = result.JsonSerializeThis();
            }
            catch (Exception ex)
            {
                json = "Error".JsonSerializeThis();
            }

            context.Response.Write(json);
            context.ApplicationInstance.CompleteRequest();
        
        }
        private object[] GetMethodArguments()
        {
            var arguments = new Dictionary<int, object>();
            foreach (var pair in Parameters)
            {
                MethodParameter p = pair.Value;
                if (p == null || p.ParameterDetails == null || p.ParameterDetails.ParameterType == null) continue;
                Type paramType = p.ParameterDetails.ParameterType;
                object arg;

                if (paramType.IsEnum)
                {
                    arg = Enum.Parse(paramType, p.Value);
                }
                else if (p.ParameterDetails.ParameterType.IsPrimitive)
                {
                    arg = GetPrimitiveType(paramType, p.Value);
                }
                else
                {
                    arg = GetNormalType(paramType, p.Value);
                }

                arguments.Add(p.ParameterDetails.Position, arg);
            }

            // Get the arguments in order
            var output = new object[arguments.Count];
            for (int index = 0; index < arguments.Count; index++)
            {
                output[index] = arguments[index];
            }
            return output;
        }

        private object GetNormalType(Type paramType, string value)
        {
            object arg = null;

            if (paramType == typeof(string))
            {
                arg = value;
            }
            else if (paramType == typeof(decimal))
            {
                arg = Decimal.Parse(value);
            }
            else if (paramType == typeof(DateTime))
            {
                try
                {
                    arg = DateTime.Parse(value);
                }
                catch
                {
                    try
                    {
                        // If the direct parse doesn't work then try parsing the tick value
                        arg = new DateTime(Int32.Parse(value));
                    }
                    catch (Exception ex)
                    {
                        
                    }
                }
            }

            return arg;
        }

        private static object GetPrimitiveType(Type type, string value)
        {
            object arg = null;

            type.Switch(new Func<Type, bool>[]
            {
                (delegate(Type primType)
                     {
                        if (primType == typeof(Boolean))
                            arg = Boolean.Parse(value);
                         return primType == typeof(Boolean);
                     }),
                (delegate(Type primType)
                     {
                         if (primType == typeof(Int32))
                             arg = Int32.Parse(value);
                         return primType == typeof(Int32);
                     }),
                (delegate(Type primType)
                     {
                         if (primType == typeof(Double))
                             arg = Double.Parse(value);
                         return primType == typeof(Double);
                     }),
                (delegate(Type primType)
                     {
                         if (primType == typeof(Single))
                             arg = Single.Parse(value);
                         return primType == typeof(Single);
                     })
            });

            return arg;
        }

        private bool IsSystemOption(string key, string value)
        {
            key = key ?? value;

            var systemOption = true;
            switch (key.ToLower())
            {
                case SHOW_JSON_RESULT:
                    break;
                default:
                    systemOption = false;
                    break;
            }
            return systemOption;
        }


        /// <summary>
        /// This fix a problem in .net that don't allow underscores in parameters
        /// </summary>
        public void RemoveUnderscoreParameters()
        {
            var keys = (from pair in Parameters where pair.Key.StartsWith("_") select pair.Key).ToList();
            foreach (var key in keys)
            {
                Parameters.Remove(key);
            }
        }

        /// <summary>
        /// This class contains the details regarding a single method parameter. 
        /// The information is received from .net throug reflection
        /// </summary>
        public class MethodParameter
        {
            /// <summary>
            /// Detailed info regarding the parameter
            /// </summary>
            public ParameterInfo ParameterDetails { get; set; }

            /// <summary>
            /// The value of the parameter
            /// </summary>
            public string Value { get; set; }

            /// <summary>
            /// The name of the parameter
            /// </summary>
            public string Name { get; set; }

            /// <summary>
            /// Public constructor for MethodParameter
            /// </summary>
            /// <param name="name">Name of parameter</param>
            /// <param name="value">Value of parameter</param>
            public MethodParameter(string name, string value)
            {
                Name = name;
                Value = value;
            }

            /// <summary>
            /// Public constructor for MethodParameter
            /// </summary>
            public MethodParameter()
            {
            }
        }
    }
}
